#include <ArduinoJson.h>




/*********
by Ningh 
adapted from Arduino IDE example: Examples > Arduino OTA > BasicOTA.ino 
*********/
#include <ESP8266WiFi.h> 
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include "time.h"

#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <uri/UriBraces.h>
#include <uri/UriRegex.h>
#include <ESP8266HTTPClient.h>

#include "FS.h"
#include <LittleFS.h>

#include "smartconfig.h" 

#define ADC_PIN         34

StaticJsonDocument<512> doc; 

// LED 2812
#include <NeoPixelBus.h>

#include <NeoPixelAnimator.h>

uint16_t PixelCount = 60; // this example assumes 4 pixels, making it smaller will cause a failure
const uint8_t PixelPin = 2;  // make sure to set this to the correct pin, ignored for Esp8266

 
uint16_t lastPixel = 0; // track the eye position
int8_t moveDir = 1; // track the direction of movement

int ledMode = 0; // 0 animate ; 1 static
 

int duration = random(10,50);
int speed = random(5, 500);
int mode = 0;
String htmlColor = "#000022";


// #define
int colorSaturation = 8;

const uint16_t AnimCount = 1; // we only need one
int TailLength = 12; // length of the tail, must be shorter than PixelCount
const float MaxLightness = 0.4f; // max lightness at the head of the tail (0.5f is full bright)

NeoGamma<NeoGammaTableMethod> colorGamma; // for any fade animations, best to correct gamma


const RgbColor CylonEyeColor( random(1,colorSaturation) , random(1,colorSaturation)  ,  random(1,colorSaturation)  );

RgbColor ledColor( random(1,colorSaturation) , random(1,colorSaturation)  ,  random(1,colorSaturation)  );


NeoPixelAnimator animations(AnimCount); // NeoPixel animation management object

NeoPixelBus<NeoGrbFeature, NeoEsp8266Uart1800KbpsMethod> strip(PixelCount, PixelPin);
//NeoPixelBus<NeoRgbFeature, NeoEsp8266Uart1400KbpsMethod> strip(PixelCount, PixelPin);





const char *hostname = "esp01s-led-01"; 
const char *configFilename = "/config.txt";  // <- SD library uses 8.3 filenames 


ESP8266WebServer server(80);
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";

bool isDht = false;

// Replace with your network credentials
const char* ssid = "WZLAN";
const char* password = "5582855828";

long timezone = -8;
byte daysavetime = 0;

char buff[512];
int vref = 1100;
 


String deleteFile(const char * path) {
  String ret = "";
  Serial.printf("Deleting file: %s\n", path);

  if (!LittleFS.begin()) {
    ret = ("LittleFS mount failed");
    return ret; 
  }

  if (LittleFS.remove(path)) {
    ret = ("File deleted");
  } else {
    ret = ("Delete failed");
  }
  LittleFS.end();
  return ret;
}


void showHeap(){
  long  fh = ESP.getFreeHeap();
  char  fhc[20];

  ltoa(fh, fhc, 10);
  String freeHeap = String(fhc);
  Serial.println("freeHeap: " + freeHeap);
}  



void setInitConfig(){ 
  doc["sleepInterval"] = "5000";  
}


// Saves the configuration to a file
void saveConfiguration() {
  // Delete existing file, otherwise the configuration is appended to the file  

  Serial.println ("Save Configuration..."); 

  if (!LittleFS.begin()) {
    Serial.println ("LittleFS mount failed"); 
  }

 File file = LittleFS.open(configFilename, "w+");
  if (!file) {
    Serial.println("Failed to open config file for writing"); 
    return;
  }
 
  delay(2000); // Make sure the CREATE and LASTWRITE times are different   
 
  if (!file) {
    Serial.println(F("Failed to create file"));
    return;
  }

  // Serialize JSON to file
  if (serializeJson(doc, file) == 0) {
    Serial.println(F("Failed to write to Config.txt file"));
  }

  // Close the file
  file.close();

  LittleFS.end();
}


// Loads the configuration from a file
void loadConfiguration() {   

  if (!LittleFS.begin()) {
    Serial.println ("LittleFS mount failed"); 
  }
  
  Serial.println("load Configuration ...");

  File file = LittleFS.open(configFilename, "r");
  if (!file) {
    Serial.println ("Failed to open config file for reading, using default configuration"); 
    setInitConfig();
    saveConfiguration();
    return;
  }  

  // Deserialize the JSON document
  DeserializationError error = deserializeJson(doc, file);  
  
  // Close the file (Curiously, File's destructor doesn't close the file)
  file.close();
  
  LittleFS.end();
  
  if (error){
    Serial.println(F("Failed to read file, using default configuration"));
    deleteFile(configFilename);
    setInitConfig();
    saveConfiguration();
  } 

}


void _info( char* str){
  Serial.println(str);
}


void handleRoot() {
  
  char temp[40096];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;

  snprintf(temp, 40096,

           "<html>\
  <head>\
    <meta http-equiv='refresh' content='5'/>\
    <title>ESP32 Demo</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
    <h1>Hello from ESP8266!</h1>\
    <p>Uptime: %02d:%02d:%02d</p>\
    <p></p>\
    <p>GET /fs/append/{path}</p>\
    <p>GET /fs/read/{path}</p>\
    <p>GET /fs/write/{path}</p>\
    <p>GET /fs/delete/{path}</p>\
    <p>GET /config/read/{key}</p>\
    <p>GET /config/write/{key}/{value}</p>\
    <p>GET /on/{pin}</p>\
    <p>GET /off/{pin}</p>\ 
    <p>GET /upgrade</p>\
  </body>\
</html>", 

           hr, min % 60, sec % 60
          );
  server.send(200, "text/html", temp);
  
}
 

void printMillis() {
  Serial.print(F("millis() = "));  // show that millis() isn't correct across most Sleep modes
  Serial.println(millis());
  Serial.flush();  // needs a Serial.flush() else it may not print the whole message before sleeping
} 

void autoSleep(){     
    static uint64_t sleepTimeStamp = 0;
    int di = doc["sleepInterval"].as<int>();
    if (millis() - sleepTimeStamp > di ) {
       sleepTimeStamp = millis();
       _info("start autoSleep"); 

      Serial.println(F("going into Deep Sleep now..."));
      printMillis();  // show time difference across sleep 
//      ESP.deepSleep(7E6, WAKE_RF_DEFAULT); // good night!  D0 fires a reset in 10 seconds...
    }
}

String getVoltage(int pin)
{   
  float volts = ESP.getVcc();
  
  Serial.printf("The internal VCC reads %1.2f volts\n", volts / 1000);
  
  String voltage = "getVoltage: " + String(  volts / 1000 );
  Serial.println(voltage);
  
//    Serial.println("getVoltage: "+ String(pin) );
//    uint16_t v = analogRead(pin | ADC_PIN);
//    float battery_voltage = ((float)v / 4095.0) * 2.0 * 3.3 * (vref / 1000.0);
//    String voltage = "Voltage :" + String(battery_voltage) + "V";
//    Serial.println(voltage);
    return voltage;     
}
 


String writeFile(const char * path, const char * message) {
  String ret = "";
  Serial.printf("Writing file: %s\n", path);

  if (!LittleFS.begin()) {
    ret = ("LittleFS mount failed");
    return ret;
  }

  File file = LittleFS.open(path, "w+");
  if (!file) {
    ret = ("Failed to open file for writing");
    return ret;
  }
  if (file.print(message)) {
    ret = ("File written");
  } else {
    ret = ("Write failed");
  }
  delay(2000); // Make sure the CREATE and LASTWRITE times are different
  file.close();
  
  LittleFS.end();
  
  return ret;
}

String appendFile(const char * path, const char * message) {
  String ret = "";
  Serial.printf("Appending to file: %s\n", path);

  if (!LittleFS.begin()) {
    ret = ("LittleFS mount failed");
    return ret;
  }

  File file = LittleFS.open(path, "a");
  if (!file) {
    ret = ("Failed to open file for appending");
    return ret;
  }
  if (file.print(message)) {
    ret = ("Message appended");
  } else {
    ret = ("Append failed");
  }
  file.close();
  
  LittleFS.end();
  
  return ret;
}

String readFile(const char * path) {
  String ret = "";
  Serial.printf("Reading file: %s\n", path);

  if (!LittleFS.begin()) {
    ret = ("LittleFS mount failed");
    return ret;
  }

  File file = LittleFS.open(path, "r");
  if (!file) {
    ret = ("Failed to open file for reading");
    return ret;
  }

  Serial.print("Read from file: ");
  ret = file.readString();
  file.close();
  
  LittleFS.end();
  
  return ret;
}



bool getLocalTime(struct tm * info, uint32_t ms) {
  uint32_t count = ms / 10;
  time_t now;

  time(&now);
  localtime_r(&now, info);

  if (info->tm_year > (2016 - 1900)) {
    return true;
  }

  while (count--) {
    delay(10);
    time(&now);
    localtime_r(&now, info);
    if (info->tm_year > (2016 - 1900)) {
      return true;
    }
  }
  return false;
}



String localTime(bool escape){
  struct tm tmstruct;
    if(!getLocalTime(&tmstruct, 1000)){
    Serial.println("Failed to obtain time");
    return "";
  }
  
//  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
    Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
  char ret[50]; 
  //sprintf(ret, "%d-%02d-%02d %02d:%02d:%02d", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);

  if(escape){
      sprintf(ret, "%d-%02d-%02d%%20%02d%%3A%02d%%3A%02d", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
  }else{
      sprintf(ret, "%d-%02d-%02d %02d:%02d:%02d", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
  }
  return ret;
  // asctime(&tmstruct);  
}



 
 




bool autoConfig()
{
  
  Serial.println("AutoConfig start...");
  
  WiFi.begin();
  for (int i = 0; i < 20; i++)
  {
    int wstatus = WiFi.status();
    if (wstatus == WL_CONNECTED)
    {
      Serial.println("AutoConfig Success");
      Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
      Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
      WiFi.printDiag(Serial);
      return true;
      //break;
    }
    else
    {
      Serial.print("AutoConfig Waiting......");
      Serial.println(wstatus);
      delay(1000);
    }
  }
  Serial.println("AutoConfig Faild!" );
  return false;
  //WiFi.printDiag(Serial);
}
void smartConfig()
{
  WiFi.mode(WIFI_STA);
  Serial.println("\r\nWait for Smartconfig");
  
  WiFi.setAutoConnect(true);  // 设置自动连接
  WiFi.beginSmartConfig();
  while (1)
  {
    Serial.print(".");
    if (WiFi.smartConfigDone())
    {
      Serial.println("SmartConfig Success");
      Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
      Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
      WiFi.setAutoConnect(true);  // 设置自动连接
      break;
    }
    delay(1000); // 这个地方一定要加延时，否则极易崩溃重启
  }
}

 









void SetRandomSeed()
{
    uint32_t seed;

    // random works best with a seed that can use 31 bits
    // analogRead on a unconnected pin tends toward less than four bits
    seed = analogRead(0);
    delay(1);

    for (int shifts = 3; shifts < 31; shifts += 3)
    {
        seed ^= analogRead(0) << shifts;
        delay(1);
    }

    // Serial.println(seed);
    randomSeed(seed);
}

void LoopAnimUpdate(const AnimationParam& param)
{
    // wait for this animation to complete,
    // we are using it as a timer of sorts
    if (param.state == AnimationState_Completed)
    {
        // done, time to restart this position tracking animation/timer
        animations.RestartAnimation(param.index);


        // rotate the complete strip one pixel to the right on every update
        strip.RotateRight(1);
    }
}

void DrawTailPixels()
{
    // using Hsl as it makes it easy to pick from similiar saturated colors
    float hue = random(360) / 360.0f;
    for (uint16_t index = 0; index < strip.PixelCount() && index <= TailLength; index++)
    {
        float lightness = index * MaxLightness / TailLength;
        RgbColor color = HslColor(hue, 1.0f, lightness);

        strip.SetPixelColor(index, colorGamma.Correct(color));
    }
}

void DrawStaticPixels(){
    RgbColor color = ledColor;
    for (uint16_t index = 0; index < strip.PixelCount(); index++)
    {
        strip.SetPixelColor(index, ledColor);
    }
    strip.Show();
}



void startServer(){
  
  server.on("/", handleRoot);
  
  server.on("/upgrade", HTTP_GET, []() {    
    _info("GET /upgrade");
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
     
  server.on(UriBraces("/led/duration/{}"), []() {
    _info("GET /led/duration/{int}");
    duration = server.pathArg(0).toInt();
    String v = String(duration);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/led/speed/{}"), []() {
    _info("GET /led/speed/{int}");
    speed = server.pathArg(0).toInt();
    animations.ChangeAnimationDuration(0, speed);
    String v = String(speed);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/led/bright/{}"), []() {
    _info("GET /led/bright/{int}");
    colorSaturation = server.pathArg(0).toInt();
    String v = String(colorSaturation);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/led/color/{}/{}/{}"), []() {
    _info("GET /led/color/{red}/{green}/{blue}");
    int r = server.pathArg(0).toInt();
    int g = server.pathArg(1).toInt();
    int b = server.pathArg(2).toInt();
    ledColor = RgbColor(r,g,b);
    String v = String(r) + "," + String(g) + "," + String(b);
    server.send(200, "text/plain", v);
    if(ledMode==1){
      DrawStaticPixels();
    }
  });  
  
  
  server.on(UriBraces("/led/animate/speed/{}"), []() {
    _info("GET /led/animate/speed/{int}");
    speed = server.pathArg(0).toInt();
    animations.ChangeAnimationDuration(0, speed);
    String v = String(speed);
    server.send(200, "text/plain", v);
  });  
  
  
  server.on(UriBraces("/led/animate/stop"), []() {
    _info("GET /led/animate/stop"); 
    animations.StopAll(); 
    server.send(200, "text/plain", "StopAll");
  });  
  
  server.on(UriBraces("/led/animate/stop/{}"), []() {
    _info("GET /led/animate/stop/{int}");
    int i = server.pathArg(0).toInt();
    animations.StopAnimation(i);
    String v = String(i);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/led/animate/start/{}"), []() {
    _info("GET /led/animate/start/{int}");
    int i = server.pathArg(0).toInt();
    animations.StartAnimation(0, speed, LoopAnimUpdate);
    String v = String(i);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/led/animate/pause"), []() {
    _info("GET /led/animate/Pause"); 
    animations.Pause(); 
    server.send(200, "text/plain", "Paused");
  });  
  
  server.on(UriBraces("/led/animate/resume"), []() {
    _info("GET /led/animate/resume"); 
    animations.Resume(); 
    server.send(200, "text/plain", "Resume");
  });  
  
  server.on(UriBraces("/led/color/{}"), []() {
    _info("GET /led/color/{int}");
    colorSaturation = server.pathArg(0).toInt();
    String v = String(colorSaturation);
    server.send(200, "text/plain", v);
  });  

  
  
  server.on(UriBraces("/led/htmlColor/{}"), []() {
    _info("GET /led/htmlColor/{int}");
    htmlColor = server.pathArg(0);
    String v = htmlColor;
    server.send(200, "text/plain", v);
  });  
  

  
  server.on(UriBraces("/led/mode/{}"), []() {
    _info("GET /led/mode/{int}");
    int oldMode = ledMode;
    ledMode = server.pathArg(0).toInt(); 
    String v = String(ledMode);
    server.send(200, "text/plain", v);
    if( ledMode == oldMode ) return;
    if(ledMode==0){      
      DrawTailPixels();
      animations.StartAnimation(0, speed, LoopAnimUpdate);
    }else{      
      animations.StopAll();
    }
  });    
    
  server.on(UriBraces("/led/quantity/{}"), []() {
    _info("GET /led/quantity/{int}");
    PixelCount = server.pathArg(0).toInt();
    String v = String(PixelCount);
    server.send(200, "text/plain", v);
  });  
    
  server.on(UriBraces("/led/tail/length/{}"), []() {
    _info("GET /led/tail/length/{int}");
    TailLength = server.pathArg(0).toInt();
    String v = String(TailLength);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/voltage/{}"), []() {
    _info("GET /voltage/{pin}");
    int pin = server.pathArg(0).toInt();
    String v = getVoltage(pin);
    server.send(200, "text/plain", v);
  });  
  
  server.on(UriBraces("/config/read/{}"), []() {
    _info("GET /config/read/{key}");

    String key = server.pathArg(0);
    loadConfiguration();
    String value = doc[key];
    
    server.send(200, "text/plain", value);
  });   // /config/read
  
  
  server.on(UriRegex("^\\/config\\/write\\/([a-zA-Z]+)\\/(.+)$"), []() {
    _info("GET /config/write/{key}/{value}");

    String key = server.pathArg(0);
    String value = server.pathArg(1);
    doc[key] = value;
    
    saveConfiguration();
    
    server.send(200, "text/plain", value);
  });   // /config/read
  
  server.on(UriRegex("^\\/fs\\/read\\/(.+)$"), []() {
    _info("GET /fs/read/{}");

    String filename = server.pathArg(0);
    
    Serial.println( filename );

    String data = readFile(filename.c_str());
    server.send(200, "text/plain", data);
    
  });  // fs/read
  
  
  server.on(UriRegex("^\\/fs\\/write\\/(.+)$"), []() {
    _info("GET /fs/write/{}");

    String filename = server.pathArg(0);
    String data = "data..";
    
    String ret = writeFile(filename.c_str(), data.c_str() );
    Serial.println( ret );
    
    server.send(200, "text/plain", ret);
  });  // /fs/write
  
  
  server.on(UriRegex("^\\/fs\\/append\\/(.+)$"), []() {
    _info("GET /fs/append/{}");

    String filename = server.pathArg(0);
    String data = "data..\n";
    
    String ret = appendFile(filename.c_str(), data.c_str() );
    Serial.println( ret );
    
    server.send(200, "text/plain", ret);
  });   // /fs/append
  
   
  
  server.on(UriRegex("^\\/fs\\/delete\\/(.+)$"), []() {
    _info("GET /fs/delete/{}");

    String filename = server.pathArg(0); 
    
    String ret = deleteFile( filename.c_str() );
    Serial.println( ret );
    
    server.send(200, "text/plain", ret);
  });   // /fs/delete
  
  server.on("/fs/format", []() {
    _info("GET /fs/format");  
      
    if (!SPIFFS.begin()) {
      Serial.println ("SPIFFS mount failed"); 
    }
  
    SPIFFS.format();    
    
    server.send(200, "text/plain", "/fs/format done.");
  });   // /fs/delete
   

  server.onNotFound([]() {
    server.send(404, "text/plain", "404 Not Found!");
  });

  server.on(UriBraces("/on/{}"), []() {
    isDht = false;
    
    String p = server.pathArg(0);
    int pin = p.toInt();
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
    server.send(200, "text/plain", "set PIN on: " + p );
  });

  server.on(UriBraces("/off/{}"), []() {
    
    isDht = false;
    
    String p = server.pathArg(0);
    
    int pin = p.toInt();
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    
    server.send(200, "text/plain", "set PIN off: " + p );
  });

  

  server.on(UriBraces("/analogRead/{}"), []() {
    
    _info("GET /analogRead/{pin}");
    
    String p = server.pathArg(0);
    
    int pin = p.toInt();
    uint16_t v = analogRead(pin); 
    
    server.send(200, "text/plain", String(v) );
    
  });

  

  server.on("/update", HTTP_POST, []() {
      server.sendHeader("Connection", "close");
      server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
      ESP.restart();
    }, []() {
      HTTPUpload& upload = server.upload();
      if (upload.status == UPLOAD_FILE_START) {
        Serial.setDebugOutput(true);
        WiFiUDP::stopAll();
        Serial.printf("Update: %s\n", upload.filename.c_str());
        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
        if (!Update.begin(maxSketchSpace)) { //start with max available size
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_WRITE) {
        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_END) {
        if (Update.end(true)) { //true to set the size to the current progress
          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
        } else {
          Update.printError(Serial);
        }
        Serial.setDebugOutput(false);
      }
      yield();
    }); // /update
  
  server.begin();
  Serial.println("HTTP server started");
   
} // start server



void setup() {

  Serial.begin(115200);
  Serial.println("Start");
  
  showHeap();
//  
  Serial.print(F("\nReset reason = "));
  String resetCause = ESP.getResetReason();
  Serial.println(resetCause);
//
//
//
  loadConfiguration();
//
  Serial.println("Status\tHumidity (%)\tTemperature (C)\t(F)\tHeatIndex (C)\t(F)");
  String thisBoard= ARDUINO_BOARD;
  Serial.println(thisBoard);   

  WiFi.setAutoConnect(true); 
//
//
  if (!autoConfig())
  {
    Serial.println("Start smartConfig module");
    smartConfig();
  }

  _info("WZLAN CONNECTED");
  _info("IP address: ");
  Serial.println(WiFi.localIP());
//
//
//  Serial.printf("current hostname: %s\n", WiFi.hostname().c_str()); 
//  Serial.printf("update hostname to: ");
//  WiFi.hostname(hostname); 
//  Serial.printf("updated hostname: %s\n", WiFi.hostname().c_str());  
//
  configTime(3600 * timezone, daysavetime * 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
//
  startServer();
//
  // Hostname defaults to esp8266-[ChipID]
  ArduinoOTA.setHostname(hostname);

  ArduinoOTA.begin();
  
  _info("ArduinoOTA READYs");


  
    // LED 
    strip.Begin();
    strip.Show();

    SetRandomSeed();

    // Draw the tail that will be rotated through all the rest of the pixels
    DrawTailPixels();

    // we use the index 0 animation to time how often we rotate all the pixels
    animations.StartAnimation(0, speed, LoopAnimUpdate); 
  
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();  

  
   animations.UpdateAnimations();
   strip.Show();
   
}